package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author: 李旭
 * @create: 2021-09-11 15:07
 *
 *   MQ 生产者 应答 配置实现类
 **/
@Component //实例化
@Slf4j  // 企业上班时候使用的日志  此日志目前是世面上Java版的最好日志
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback,
                                             RabbitTemplate.ReturnCallback{

    //问题： MQProducerAckConfig的构造器 与 @Autowired 优先级谁高？
    //构造器 优先级高
    //Null空指针异常
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    //方法  后置  Spring框架课的时候  前置 后置
    @PostConstruct //它的作用 ：Bean初始化方法后执行
    public void MQProducerAckConfig(){
        rabbitTemplate.setConfirmCallback(this);//this:MQProducerAckConfig
        rabbitTemplate.setReturnCallback(this);
    }

    //交换机应答 方法     成功或失败都应答
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //每次发消息 此方法都会被执行
        /**
         * ack :  true:交换机成功应答
         *        false:交换机失败应答
         * cause： 成功了：null
         *         失败了：失败的原因
         * CorrelationData：附加对象
         *        里面：交换机名称 、RoutingKey、Message、是否是延迟消息 延迟时间 ..
         */
        if(ack){
            log.info("交换机成功应答：" + cause);
        }else{
            log.error("交换机失败应答：失败的原因：" + cause);
            //重新发送消息
            //不是无限重新发送  在企业 发3次或2次 还不成功的话
            // 只能写日志 由工作人员去处理
            retrySendMessage(correlationData);

        }
    }

    //重新发消息
    private void retrySendMessage(CorrelationData correlationData) {
        //附加对象
        GmallCorrelationData gmallCorrelationData =
                (GmallCorrelationData)correlationData;
        //判断重新发送几次了
        int retryCount = gmallCorrelationData.getRetryCount();
        if(retryCount < 2){
            retryCount++;
            gmallCorrelationData.setRetryCount(retryCount);

            //更新一下缓存
            redisTemplate.opsForValue().set(gmallCorrelationData.getId(),
                    JSONObject.toJSONString(gmallCorrelationData));

            //0:发一次
            //1:发一次
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange()
            ,gmallCorrelationData.getRoutingKey(),
                    gmallCorrelationData.getMessage(),gmallCorrelationData);
            log.info("交换机重新发送消息：第" + retryCount + "次数");
        }else{
            // 打印日志 记录  重试了2次 还是发不出去

        }

    }


    //队列应答 方法  只有失败了才应答
    @Override
    public void returnedMessage(Message message, int replyCode,
                                String replyText, String exchange, String routingKey) {
        log.error("队列失败应答：消息：" + new String(message.getBody()));
        log.error("队列失败应答：失败码：" + replyCode);
        log.error("队列失败应答：失败消息：" + replyText);
        log.error("队列失败应答：交换机：" + exchange);
        log.error("队列失败应答：routingKey：" + routingKey);

        //使用外力  Redis缓存来记录重发的次数
        // Redis :  2次     K ：不知道  V：GmallCorrelationData
        // GmallCorrelationData 重发次数 实现发2次
        Object uuid = message.getMessageProperties().getHeader("spring_returned_message_correlation");
        String json = (String) redisTemplate.opsForValue().get(uuid);
        GmallCorrelationData gmallCorrelationData =
                JSONObject.parseObject(json, GmallCorrelationData.class);

        //判断是否基于插件的延迟消息  如果是 不要重新发送了  如果不是呢 重新发送
        if(gmallCorrelationData.isDelayedDelay()){
            log.error("本次是基于插件的延迟消息： 不要重新发了 ");
            return;
        }
        //重新发送
        retrySendMessage(gmallCorrelationData);
    }
}
